/**
 * @file main.cpp
 * @author BadFatCat0919 (543015378@qq.com)
 * @brief 指纹锁-ESP8266固件程序入口
 * @date 2022-04-03
 */

/* 头文件 */
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <SoftwareSerial.h>
#include <math.h>
#include "ESP8266httpUpdate.h"
#include "Adafruit_SSD1306.h"
#include "NTPClient.h"
#include "PubSubClient.h"
#include "ArduinoJson.h"
#include "LED.h"
#include "Adafruit_Fingerprint.h"
#include <stdio.h>
#include <EEPROM.h>
#include <string.h>
#include <stdlib.h>

/* 定义LOG上报 */
#define LOG(format, args...) {oled.printf(format, ##args); oled.display(); Serial.printf(format, ##args);}
#define LOG_SERIAL(format, args...) {Serial.printf(format, ##args);}
#define LOG_OLED_CLEAR() {oled.clearDisplay(); oled.display(); oled.setTextSize(1); oled.setCursor(0, 0);}
#define LOG_MQTT(topic, format, args...) {char str[100];snprintf(str, sizeof(str), format, ##args);mqttClient.publish(topic"/set", str);}

/* WiFi参数设置 */
#define WIFI_SSID     "bieyafei"
#define WIFI_PASSWARD "123456789"

/* 固件升级 */
#define OTA_VERSION "V3.2"
#define OTA_URL "http://bin.bemfa.com/b/1BcYzdlOWQ5NjM3ZGUyMTdiNDBiYWU4ZDFhZmQyNzllMzE=OTA.bin"

/* 巴法云用户私钥设置 */
#define BEMFA_UID "c7e9d9637de217b40bae8d1afd279e31"

/* MQTT Topic 定义 */
#define TOPIC_OTA "OTA"
#define TOPIC_LOG "LOG"
#define TOPIC_E2W "ESPtoWC"
#define TOPIC_W2E "WCtoESP"

/**
 * @brief 时间
 */
struct Time_t {
  uint8_t hours, minutes, seconds;
  bool operator <=(const Time_t &time) const {
    if(hours < time.hours) {
      return true;
    } else if(hours == time.hours) {
      if(minutes < time.minutes) {
        return true;
      } else if(minutes == time.minutes) {
        if(seconds <= time.seconds) {
          return true;
        }
      }
    }
    return false;
  }
  bool operator >=(const Time_t &time) const {
    return time <= *this;
  }
  bool operator ==(const Time_t &time) const {
    return hours == time.hours && minutes == time.minutes && seconds == time.seconds;
  }
  bool isInRange(const Time_t &start, const Time_t &end) {
    if(start == end) return true;
    return *this >= start && *this <= end;
  }
};

/**
 * @brief 用户
 */
struct User_t {
  char name[3 * 4 + 1];
  Time_t startTime, endTime;
};
User_t user[61];
/**
 * @brief 存储用户
 * @param id 用户id
 */
void user_store(uint8_t id) {
  for(size_t i = id * sizeof(User_t), j = 0; i < (id + 1) * sizeof(User_t); i++, j++) {
    EEPROM.write(i, ((uint8_t *)(&user[id]))[j]);
  }
  while(!EEPROM.commit());
}

/* 对象实例定义 */
TwoWire iic;
Adafruit_SSD1306 oled(128, 32, &iic);
WiFiClient mqttClient_WiFiClient;
PubSubClient mqttClient(mqttClient_WiFiClient);
WiFiUDP NTP_WiFiUDP;
NTPClient NTP(NTP_WiFiUDP, "ntp1.aliyun.com", 8 * 3600, 60000);
Time_t NTP_Time;
LED ledW, ledR(16), relay(5, HIGH);
SoftwareSerial finger_Serial(4, 15);
Adafruit_Fingerprint finger(&finger_Serial);

/**
 * @brief 进行OTA固件升级
 */
void OTA_update(){
  WiFiClient UpdateClient;
  ledR.on();
  ledW.on();
  LOG_OLED_CLEAR();
  oled.setTextSize(2);
  LOG("OTA update running.");
  LOG_MQTT(TOPIC_OTA, "固件升级中...");
  switch(ESPhttpUpdate.update(UpdateClient, OTA_URL)) {
    /*固件升级失败*/
    case HTTP_UPDATE_FAILED:
        LOG_SERIAL("ERROR: OTA UPDATE FILED!");
        break;

    /*不需要升级*/
    case HTTP_UPDATE_NO_UPDATES:
        LOG_SERIAL("No need to update.");
        break;

    /*固件升级成功*/
    case HTTP_UPDATE_OK:
        LOG_SERIAL("OTA update OK.\r\nRebooting...");
        break;
  }
  ledR.off();
  ledW.off();
}

/**
 * @brief 订阅的主题有新的消息时的处理函数
 * @param topic 消息主题
 * @param payload 消息内容
 * @param length 消息长度
 */
void mqttClient_Callback(char* topic, byte* payload, unsigned int length) {
  char message[length + 1];
  strncpy(message, (const char *)payload, length);
  message[length] = '\0';
  LOG_SERIAL("MQTT message:\r\n<%s> \"%s\"\r\n", topic, message);
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, message);
  if(error)
  {
    LOG_SERIAL("Did not find JSON string!\r\n");
  } else {
    if(!strcmp(topic, TOPIC_W2E)) {
      const char * cmd = doc["cmd"];
      if(cmd != nullptr) {
        /* 指纹注册指令 */
        if(!strcmp(cmd, "enroll")) {
          uint8_t id = doc["id"];
          const char * name = doc["name"];
          strcpy(user[id].name, name);
          user[id].startTime.hours = doc["sh"];
          user[id].startTime.minutes = doc["sm"];
          user[id].endTime.hours = doc["eh"];
          user[id].endTime.minutes = doc["em"];
          
          oled.clearDisplay();
          oled.setTextSize(2);
          oled.setCursor(0, 0);
          oled.printf("Enroll [%d]\r\n", id);
          oled.setCursor(0, 16);
          oled.printf("Put finger\r\n");
          oled.display();
          do {
            while (finger.getImage() != FINGERPRINT_OK) {
              yield();
            }
          } while(finger.image2Tz(1) != FINGERPRINT_OK);

          oled.clearDisplay();
          oled.setCursor(0, 0);
          oled.printf("Enroll [%d]\r\n", id);
          oled.setCursor(0, 16);
          oled.printf("  Remove\r\n");
          oled.display();
          while (finger.getImage() != FINGERPRINT_NOFINGER) {
            yield();
          }

          oled.clearDisplay();
          oled.setCursor(0, 0);
          oled.printf("Enroll [%d]\r\n", id);
          oled.setCursor(6, 16);
          oled.printf("Put again\r\n");
          oled.display();
          do {
            while (finger.getImage() != FINGERPRINT_OK) {
              yield();
            }
          } while(finger.image2Tz(2) != FINGERPRINT_OK);

          uint8_t ret = finger.createModel();
          if(ret != FINGERPRINT_OK) {
            LOG_OLED_CLEAR();
            LOG("Error! Code:[%d]\r\n", ret);
          } else {
            ret = finger.storeModel(id);
            if(ret != FINGERPRINT_OK) {
              LOG_OLED_CLEAR();
              LOG("Error! Code:[%d]\r\n", ret);
              LOG_MQTT(TOPIC_LOG, "用户 [%s(id:%d)] 指纹注册失败，错误码 [%d]", user[id].name, id, ret);
            } else {
              user_store(id);
              LOG_MQTT(TOPIC_LOG, "用户 [%s(id:%d)] 指纹已成功注册", user[id].name, id);
              oled.clearDisplay();
              oled.setCursor(0, 0);
              oled.printf("Enroll [%d]\r\n", id);
              oled.setCursor(6, 16);
              oled.printf(" Succeed\r\n");
              oled.display();
              delay(1000);
              LOG_OLED_CLEAR();
            }
          }
        /* 用户访问时间设置指令 */
        } else if(!strcmp(cmd, "set_id_time")) {
          uint8_t id = doc["id"];
          if(id) {
            user[id].startTime.hours = doc["sh"];
            user[id].startTime.minutes = doc["sm"];
            user[id].endTime.hours = doc["eh"];
            user[id].endTime.minutes = doc["em"];
            user_store(id);
            LOG_MQTT(TOPIC_LOG, "用户 [%s(id:%d)] 允许访问时间设置为 [%02d:%02d:%02d - %02d:%02d:%02d]",
              user[finger.fingerID].name, finger.fingerID,
              user[finger.fingerID].startTime.hours, user[finger.fingerID].startTime.minutes, user[finger.fingerID].startTime.seconds,
              user[finger.fingerID].endTime.hours, user[finger.fingerID].endTime.minutes, user[finger.fingerID].endTime.seconds);
          }
        /* 指纹删除指令 */
        } else if(!strcmp(cmd, "delete")) {
          uint8_t id = doc["id"];
          if(id) {
            uint8_t ret = finger.deleteModel(id);
            LOG_MQTT(TOPIC_LOG, "用户 [%s(id:%d)] 指纹删除程序执行完毕，执行结果为 [%d]", user[id].name, id, ret);
            memset(&user[id], 0, sizeof(User_t));
            user_store(id);
          } else {
            for(uint8_t id = 1; id <= 60; id++) {
              finger.deleteModel(id);
            }
            memset(user, 0, sizeof(user));
            strncpy((char *)user, OTA_VERSION, 5);
            for(size_t i = 0; i < sizeof(user); i++) {
              EEPROM.write(i, ((uint8_t *)user)[i]);
            }
            while(!EEPROM.commit());
            LOG_MQTT(TOPIC_LOG, "已删除所有用户数据");
          }
        /* 固件升级指令 */
        } else if(!strcmp(cmd, "ota")) {
          LOG_MQTT(TOPIC_LOG, "固件升级中...")
          OTA_update();
        }
      }
    }
  }
}

/**
 * @brief Arduino框架的软硬件初始化接口
 */
void setup()
{
  /* 用于组织系统软硬件初始化流程的有限状态机 */
  struct {
    typedef enum {
      BEGIN,           // 起始
      OLED_INIT,       // OLED初始化
      FPM_INIT,        // 指纹模块初始化
      FPM_CHECKING,    // 检测指纹模块
      EEPROM_INIT,     // 存储器初始化
      WIFI_INIT,       // WiFi初始化
      WIFI_CONNECTING, // 连接WiFi
      NTP_INIT,        // NTP客户端初始化
      NTP_CONNECTING,  // 连接NTP服务器
      MQTT_INIT,       // MQTT客户端初始化
      MQTT_CONNECTING, // 连接MQTT服务器
      MQTT_SUBSCRIBE,  // MQTT订阅主题
      ERROR,           // 出错
      FINAL,           // 终止
    }StatusType;
    StatusType status = BEGIN;
    StatusType errorStatus = BEGIN;
    bool runFlag = true;

    /**
     * @brief 状态转移
     * @param nextStatus 将要转移的状态
     */
    inline void stateTransition(StatusType nextStatus) {
      status = nextStatus;
    }
    /**
     * @brief 进入出错状态并记录错误来源
     */
    inline void error(void) {
      errorStatus = status;
      stateTransition(ERROR);
    }
  } DFA_Setup;

  while(DFA_Setup.runFlag) {
    switch(DFA_Setup.status) {
    /* 起始 */
    case DFA_Setup.BEGIN:
      Serial.begin(9600);
      Serial.println();
      ledW.init().on();
      ledR.init().on();
      relay.init().off();
      DFA_Setup.stateTransition(DFA_Setup.OLED_INIT); // 状态转移 => OLED初始化
      break;

    /* OLED初始化 */
    case DFA_Setup.OLED_INIT:
      iic.begin(12, 14);
      oled.begin(SSD1306_SWITCHCAPVCC, 0X3C);
      oled.setCursor(0, 0);
      oled.setTextSize(1);
      oled.setTextColor(SSD1306_WHITE);
      oled.clearDisplay();
      oled.display();
      DFA_Setup.stateTransition(DFA_Setup.FPM_INIT); // 状态转移 => 指纹模块初始化
      break;
    
    /* 指纹模块初始化 */
    case DFA_Setup.FPM_INIT:
      finger.begin(57600);
      LOG("System init <1/6>\r\n");
      LOG("Check FPM...");
      DFA_Setup.stateTransition(DFA_Setup.FPM_CHECKING); // 状态转移 => 检测指纹模块
      break;
    
    /* 检测指纹模块 */
    case DFA_Setup.FPM_CHECKING:
      if(finger.verifyPassword())
      {
        finger.getParameters();
        LOG("\r\nSucceed.\r\n");
        LOG_SERIAL("Status: [%c]\r\n", finger.status_reg == 0 ? 'O' : 'X');
        LOG_SERIAL("Sys ID: [%XH]\r\n", finger.system_id);
        LOG_SERIAL("Capacity: [%d]\r\n", finger.capacity);
        LOG_SERIAL("Security level: [%d]\r\n", finger.security_level);
        LOG_SERIAL("Device address: [%XH]\r\n", finger.device_addr);
        LOG_SERIAL("Packet len: [%d]\r\n", finger.packet_len);
        LOG_SERIAL("Baud rate: [%d]\r\n", finger.baud_rate);
        delay(1000);
        LOG_OLED_CLEAR();
        DFA_Setup.stateTransition(DFA_Setup.EEPROM_INIT); // 状态转移 => 存储器初始化
        break;
      }
      delay(500);
      LOG_SERIAL(".");
      break;

    /* 存储器初始化 */
    case DFA_Setup.EEPROM_INIT:
      LOG("System init <2/6>\r\n");
      LOG("Read EEPROM...");
      EEPROM.begin(sizeof(user));
      for(size_t i = 0; i < sizeof(user); i++) {
        ((uint8_t *)user)[i] = EEPROM.read(i);
        if(i % 400 == 0) {
          oled.print('.');
          oled.display();
        }
      }
      if(strncmp((const char *)user, "ELock", 6)) {
        LOG_SERIAL("\r\nClear ID and refresh EEPROM!");
        for(uint8_t id = 1; id <= 60; id++) {
          finger.deleteModel(id);
        }
        memset(user, 0, sizeof(user));
        strncpy((char *)user, "ELock", 6);
        for(size_t i = 0; i < sizeof(user); i++) {
          EEPROM.write(i, ((uint8_t *)user)[i]);
        }
        while(!EEPROM.commit());
      } else {
        LOG_SERIAL("\r\nUser data:\r\n");
        LOG_SERIAL("[id]\t[name]\t[start time]\t[end time]\r\n");
        for(uint8_t id = 1; id <= 60; id++) {
          if(user[id].name[0] != 0) {
            LOG_SERIAL(" %02d\t%s\t  %02d:%02d:00\t %02d:%02d:00\r\n", id, user[id].name,
            user[id].startTime.hours, user[id].startTime.minutes, 
            user[id].endTime.hours, user[id].endTime.minutes);
          }
        }
      }
      LOG("\r\nSucceed.\r\n");
      delay(500);
      LOG_OLED_CLEAR();
      DFA_Setup.stateTransition(DFA_Setup.WIFI_INIT); // 状态转移 => WiFi初始化
      break;

    /* WiFi初始化 */
    case DFA_Setup.WIFI_INIT:
      WiFi.mode(WIFI_STA);
      WiFi.begin(WIFI_SSID, WIFI_PASSWARD);
      WiFi.setAutoConnect(true);
      WiFi.setAutoReconnect(true);
      LOG("System init <3/6>\r\n");
      LOG("<%s>\r\nConnect WiFi...", WIFI_SSID);
      DFA_Setup.stateTransition(DFA_Setup.WIFI_CONNECTING); // 状态转移 => 连接WiFi
      break;

    /* 连接WiFi */
    case DFA_Setup.WIFI_CONNECTING:
      if(WiFi.isConnected()) {
        LOG("\r\nIP <%s>\r\n", WiFi.localIP().toString().c_str());
        delay(1000);
        LOG_OLED_CLEAR();
        DFA_Setup.stateTransition(DFA_Setup.NTP_INIT); // 状态转移 => NTP客户端初始化
        break;
      }
      LOG_SERIAL(".");
      delay(500);
      break;

    /* NTP客户端初始化 */
    case DFA_Setup.NTP_INIT:
      NTP.begin();
      LOG("System init <4/6>\r\n");
      LOG("Connect NTP server...");
      DFA_Setup.stateTransition(DFA_Setup.NTP_CONNECTING); // 状态转移 => 连接NTP服务器
      break;

    /* 连接NTP服务器 */
    case DFA_Setup.NTP_CONNECTING:
      if(NTP.update()) {
        LOG("\r\nNTP Time <%s>\r\n", NTP.getFormattedTime().c_str());
        delay(1000);
        LOG_OLED_CLEAR();
        DFA_Setup.stateTransition(DFA_Setup.MQTT_INIT); // 状态转移 => MQTT客户端初始化
        break;
      }
      LOG_SERIAL(".");
      delay(500);
      break;

    /* MQTT客户端初始化 */
    case DFA_Setup.MQTT_INIT:
      mqttClient.setServer("bemfa.com", 9501).setCallback(mqttClient_Callback);
      LOG("System init <5/6>\r\n");
      LOG("Connect MQTT cloud...");
      DFA_Setup.stateTransition(DFA_Setup.MQTT_CONNECTING); // 状态转移 => 连接MQTT服务器
      break;

    /* 连接MQTT服务器 */
    case DFA_Setup.MQTT_CONNECTING:
      if(mqttClient.connected()) {
        LOG("\r\nSucceed.\r\n");
        delay(1000);
        LOG_OLED_CLEAR();
        LOG("System init <6/6>\r\n");
        LOG("MQTT subscribe...");
        DFA_Setup.stateTransition(DFA_Setup.MQTT_SUBSCRIBE); // 状态转移 => MQTT订阅主题
        break;
      }
      if(!mqttClient.connect(BEMFA_UID)) {
        LOG_OLED_CLEAR();
        LOG("ERROR:\r\nMQTT CONNECT FAILED!\r\nCODE <%d>", mqttClient.state());
        DFA_Setup.stateTransition(DFA_Setup.ERROR);
        DFA_Setup.error(); // 状态转移 => 出错
        break;
      }
      LOG_SERIAL(".");
      delay(100);
      break;

    /* MQTT订阅主题 */
    case DFA_Setup.MQTT_SUBSCRIBE:
      if(mqttClient.subscribe(TOPIC_OTA) 
      && mqttClient.subscribe(TOPIC_LOG) 
      && mqttClient.subscribe(TOPIC_E2W) 
      && mqttClient.subscribe(TOPIC_W2E)) {
        LOG("\r\nSucceed.\r\n");
        delay(1000);
        LOG_OLED_CLEAR();
        DFA_Setup.stateTransition(DFA_Setup.FINAL); // 状态转移 => 终止
        break;
      }
      LOG_SERIAL(".");
      delay(500);
      break;

    /* 终止 */
    case DFA_Setup.FINAL:
      LOG_OLED_CLEAR();
      LOG("System init succeed.\r\n");
      LOG_MQTT(TOPIC_LOG, "指纹锁已上线，固件版本号 [%s]", OTA_VERSION);
      ledW.on(300);
      ledR.off();
      delay(700);
      LOG_OLED_CLEAR();
      oled.setTextSize(2);
      oled.setCursor(18, 3);
      oled.printf("ELock");
      oled.setTextSize(1);
      oled.setCursor(92, 10);
      oled.printf(OTA_VERSION);
      oled.setCursor(38, 23);
      oled.drawFastHLine(0, 20, 128, SSD1306_WHITE);
      oled.printf("Designed by BYF");
      oled.display();
      delay(2000);
      DFA_Setup.runFlag = false;
      LOG_OLED_CLEAR();
      break;

    /* 出错 */
    default:
    case DFA_Setup.ERROR:
      LOG_OLED_CLEAR();
      oled.setTextSize(2);
      LOG("ERROR!\r\nSTATUS<%d>\r\n", DFA_Setup.errorStatus);
      delay(5000);
      ESP.restart();
      break;
    }
  }
}

/**
 * @brief Arduino框架的主循环接口
 */
void loop()
{
  /* 用于组织系统工作流程的有限状态机 */
  static struct {
    typedef enum {
      BEGIN,        // 起始
      WIFI_CHECK,   // 检查WiFi连接状态
      NTP_HANDLE,   // NTP时钟同步
      MQTT_HANDLE,  // 处理MQTT服务
      FINGER_SCAN, // 指纹扫描
      OLED_DISPLAY, // OLED显示
      ERROR,        //出错
    }StatusType;
    StatusType status = BEGIN;
    StatusType errorStatus = BEGIN;

    /**
     * @brief 状态转移
     * @param nextStatus 将要转移的状态
     */
    inline void stateTransition(StatusType nextStatus) {
      status = nextStatus;
    }
    /**
     * @brief 进入出错状态并记录错误来源
     */
    inline void error(void) {
      errorStatus = status;
      stateTransition(ERROR);
    }
  } DFA_MainLoop;

  switch (DFA_MainLoop.status)
  {
  /* 起始 */
  case DFA_MainLoop.BEGIN:
    oled.clearDisplay();
    DFA_MainLoop.stateTransition(DFA_MainLoop.WIFI_CHECK); // 状态转移 => 检查WiFi连接状态
    break;

  /* 检查WiFi连接状态 */
  case DFA_MainLoop.WIFI_CHECK:
    {
      static uint8_t errCnt = 0;
      if(!WiFi.isConnected()) {
        ledW.on();
        if(errCnt++ >= 1000)
        {
          DFA_MainLoop.error(); // 状态转移 => 出错
          break;
        } else {
          delay(500);
          break;
        }
      }
      else
      {
        ledW.off();
        errCnt = 0;
      }
    }
    DFA_MainLoop.stateTransition(DFA_MainLoop.NTP_HANDLE); // 状态转移 => NTP时钟同步
    break;

  /* NTP时钟同步 */
  case DFA_MainLoop.NTP_HANDLE:
    {
      static uint8_t errCnt = 0;
      if(!NTP.update()) {
        if(errCnt++ >= 1000)
        {
          DFA_MainLoop.error(); // 状态转移 => 出错
          break;
        }
        else
        {
          delay(500);
          break;
        }
      }
      else
      {
        errCnt = 0;
      }
    }
    NTP_Time.hours = NTP.getHours();
    NTP_Time.minutes = NTP.getMinutes();
    NTP_Time.seconds = NTP.getSeconds();
    oled.setCursor(6, 0);
    oled.setTextSize(2);
    oled.printf("<%s>\r\n", NTP.getFormattedTime().c_str());
    DFA_MainLoop.stateTransition(DFA_MainLoop.MQTT_HANDLE); // 状态转移 => 处理MQTT服务
    break;

  /* 处理MQTT服务 */
  case DFA_MainLoop.MQTT_HANDLE:
    if(!mqttClient.loop()) {
      DFA_MainLoop.error(); // 状态转移 => 出错
      break;
    }
    DFA_MainLoop.stateTransition(DFA_MainLoop.FINGER_SCAN); // 状态转移 => 指纹扫描
    break;

  /* 指纹扫描 */
  case DFA_MainLoop.FINGER_SCAN:
    if(finger.getImage() == FINGERPRINT_OK) {
      if(finger.image2Tz() == FINGERPRINT_OK) {
        oled.setTextSize(2);
        if(finger.fingerSearch() == FINGERPRINT_OK) {
          if(NTP_Time.isInRange(user[finger.fingerID].startTime, user[finger.fingerID].endTime)) {
            oled.setCursor(6, 16);
            oled.printf(" [Access]\r\n");
            oled.display();
            LOG_MQTT(TOPIC_LOG, "用户 [%s(id:%d)] 打开了指纹锁", user[finger.fingerID].name, finger.fingerID);
            relay.on();
            ledW.on();
            ledR.on();
            while (finger.getImage() != FINGERPRINT_NOFINGER) {
              yield();
            }
            delay(500);
            relay.off();
            ledW.off();
            ledR.off();
          } else {
            oled.setCursor(6, 16);
            oled.printf("[Not Time]\r\n");
            oled.display();
            LOG_MQTT(TOPIC_LOG, "用户 [%s(id:%d)] 未在指定时间段(%02d:%02d:%02d - %02d:%02d:%02d)访问，访问已拒绝",
              user[finger.fingerID].name, finger.fingerID,
              user[finger.fingerID].startTime.hours, user[finger.fingerID].startTime.minutes, user[finger.fingerID].startTime.seconds,
              user[finger.fingerID].endTime.hours, user[finger.fingerID].endTime.minutes, user[finger.fingerID].endTime.seconds);
          }
        } else {
          oled.printf(" [Unmatch]\r\n");
          oled.display();
        }
      }
    }
    DFA_MainLoop.stateTransition(DFA_MainLoop.OLED_DISPLAY); // 状态转移 => OLED显示
    break;

  /* OLED显示 */
  case DFA_MainLoop.OLED_DISPLAY:
    oled.display();
    DFA_MainLoop.stateTransition(DFA_MainLoop.BEGIN); // 状态转移 => 起始
    break;
  
  /* 出错 */
  default:
  case DFA_MainLoop.ERROR:
    LOG_OLED_CLEAR();
    oled.setTextSize(2);
    LOG("ERROR!\r\nSTATUS<%d>\r\n", DFA_MainLoop.errorStatus);
    delay(5000);
    ESP.restart();
  }
}
